`include "mycpu.h"

module if_stage(
    input                          clk            ,
    input                          reset          ,
    //allwoin
    input                          ds_allowin     ,
    //brbus
    input  [`DS_TO_FS_BUS_WD -1:0] ds_to_fs_bus   ,
    input                          ws_flush       ,
    //from ws
    input  [`WS_TO_FS_BUS_WD -1:0] ws_to_fs_bus   ,
    //to ds
    output                         fs_to_ds_valid ,
    output [`FS_TO_DS_BUS_WD -1:0] fs_to_ds_bus   ,
    // inst sram interface
    output        inst_sram_req  ,
    output        inst_sram_wr   ,
    output [ 3:0] inst_sram_wstrb,
    output [ 1:0] inst_sram_size ,
    output [31:0] inst_sram_addr ,
    output [31:0] inst_sram_wdata,
    input  [31:0] inst_sram_rdata,
    input         inst_sram_addr_ok,
    input         inst_sram_data_ok,
    //tlb search port 0
    output [18:0] vpn2,
    output        odd_page,
    input         found,
    input  [19:0] pfn,
    input  [ 2:0] c,
    input         d,
    input         v
);

reg         fs_valid;
wire        fs_ready_go;
wire        pfs_ready_go;
wire        fs_allowin;
wire        to_fs_valid;

wire [31:0] seq_pc;
wire [31:0] nextpc;
wire [31:0] final_addr;

wire         br_stall;
wire         br_taken;
wire [ 31:0] br_target;
reg  [ 33:0] br_reg;
reg                    br_reg_valid;
reg                    bd_undone;
always @(posedge clk) begin
    if(reset || ws_flush)
        br_reg_valid <= 1'b0;
    else if(ds_to_fs_bus[32] && !fs_valid)
        br_reg_valid <= 1'b1;
    else if(pfs_ready_go && !bd_undone)
        br_reg_valid <= 1'b0;
    else if(br_taken)
        br_reg_valid <= 1'b1;
    
    if(ds_to_fs_bus[32])
        br_reg <= ds_to_fs_bus[33:0];
    
    if(reset || ws_flush)
        bd_undone <= 1'b0;
    else if(pfs_ready_go)
        bd_undone <= 1'b0;
    else if(ds_to_fs_bus[32] && !fs_valid)
        bd_undone <= 1'b1;
end
assign {br_stall,br_taken,br_target} = br_reg_valid? br_reg : ds_to_fs_bus[33:0];


wire [31:0] exl_pc;
wire        has_int;
wire        ds_retake;
reg         inst_retake;
assign ds_retake = ds_to_fs_bus[34];
assign {has_int,    //32:32
        exl_pc      //31:0
       } = ws_to_fs_bus;
always @(posedge clk) begin
    if(reset || ws_flush)
        inst_retake <= 1'b0;
    else if(ds_retake)
        inst_retake <= 1'b1;
end

//tlb
wire unmapped;

//exceptions
wire fs_ex;
wire fs_adel;
wire fs_tlbl;
wire [4:0] fs_excode;
wire tlb_refill;
reg        flush_pc_valid;
reg [31:0] flush_pc;
reg        cancel;
reg        req_wait_valid;
reg [31:0] req_wait;
reg        ex_pc_valid;


reg  [31:0] fs_inst;
reg         fs_inst_valid;
wire [31:0] to_ds_inst;
reg  [31:0] fs_pc;
wire [31:0] to_ds_pc;
wire        to_ds_ex;
assign to_ds_inst = {32{fs_inst_valid}} & fs_inst | {32{!fs_inst_valid}} & inst_sram_rdata;
assign to_ds_pc = {32{ex_pc_valid}} & nextpc | {32{!ex_pc_valid}} & fs_pc;
assign to_ds_ex = ex_pc_valid;
assign fs_to_ds_bus = {tlb_refill,
                       inst_retake,
                       to_ds_ex   ,
                       fs_excode,
                       to_ds_inst ,
                       to_ds_pc   };


//exceptions
assign fs_adel = !(fs_pc[1:0] == 2'd0);
assign fs_tlbl = !unmapped && (!found || found && !v);
assign fs_ex = fs_adel || has_int || fs_tlbl;
assign fs_excode = has_int? `EX_INT : 
                   fs_adel? `EX_ADEL: {5{fs_tlbl}} & `EX_TLBL;
assign tlb_refill = !found && !unmapped;
always @(posedge clk) begin
    if(reset)
        flush_pc_valid <= 1'b0;
    else if(ws_flush)
        flush_pc_valid <= 1'b1;
    else if(pfs_ready_go && !req_wait_valid)
        flush_pc_valid <= 1'b0;
    
    if(reset)
        flush_pc <= 32'd0;
    else if(ws_flush)
        flush_pc <= exl_pc;
end
always @(posedge clk) begin
    if(reset)
        cancel = 1'b0;
    else if(ws_flush && (inst_sram_req || (!fs_allowin && !fs_ready_go)))
        cancel = 1'b1;
    else if(inst_sram_data_ok)
        cancel = 1'b0;
end
always @(posedge clk) begin
    if(reset)
        req_wait_valid <= 1'b0;
    else if(ws_flush && inst_sram_req && !inst_sram_addr_ok)
        req_wait_valid <= 1'b1;
    else if(inst_sram_addr_ok)
        req_wait_valid <= 1'b0;

    if(reset)
        req_wait <= 32'd0;
    else if(ws_flush)
        req_wait <= nextpc;
end
always @(posedge clk) begin
    if(reset || ws_flush)
        ex_pc_valid <= 1'b0;
    else if(fs_ex && inst_sram_data_ok)
        ex_pc_valid <= 1'b1;
end


// pre-IF stage
assign pfs_ready_go = inst_sram_req && inst_sram_addr_ok && !req_wait_valid;
assign to_fs_valid  = pfs_ready_go || fs_ex;
assign seq_pc       = fs_pc + 3'h4;
assign nextpc       = req_wait_valid? req_wait :
                      flush_pc_valid? flush_pc :
                      ((br_taken && fs_valid) || (br_reg_valid && !bd_undone))? br_target : seq_pc; 

// IF stage
assign fs_ready_go    = !cancel && (inst_sram_data_ok || fs_inst_valid) || ex_pc_valid;
assign fs_allowin     = !fs_valid || fs_ready_go && ds_allowin;
assign fs_to_ds_valid =  fs_valid && fs_ready_go;
always @(posedge clk) begin
    if (reset || ws_flush)
        fs_valid <= 1'b0;
    else if (fs_allowin)
        fs_valid <= to_fs_valid;

    if (reset)
        fs_pc <= 32'hbfbffffc;  //trick: to make nextpc be 0xbfc00000 during reset 
    else if (to_fs_valid)
        fs_pc <= nextpc;
end

//inst_sram
assign inst_sram_req   = fs_allowin && !br_stall && !fs_ex;
assign inst_sram_wr    = |inst_sram_wstrb;
assign inst_sram_wstrb = 4'h0;
assign inst_sram_size  = 2'd2;
assign inst_sram_addr  = final_addr;
assign inst_sram_wdata = 32'b0;

always @(posedge clk) begin
    if(ws_flush || reset)
        fs_inst_valid <= 1'b0;
    else if(fs_ready_go && !ds_allowin)
        fs_inst_valid <= 1'b1;
    else if(ds_allowin)
        fs_inst_valid <= 1'b0;
    
    if(fs_ready_go && !ds_allowin && !fs_inst_valid)
        fs_inst <= inst_sram_rdata;
    
end

//tlb
assign unmapped = nextpc[31] & ~nextpc[30];
assign vpn2 = nextpc[31:13];
assign odd_page = nextpc[12];
assign final_addr = {32{unmapped}} & {3'd0, nextpc[28:0]} | {32{!unmapped}} & {pfn, nextpc[11:0]};

endmodule
